//
// Created by Gorun on 2022/5/2.
//

#ifndef LOXPP_EXPR_H
#define LOXPP_EXPR_H

#include <any>
#include <memory>
#include <utility>
#include <vector>
#include "Token.h"

using namespace std;

struct Ternary;
struct Binary;
struct Grouping;
struct Literal;
struct Unary;
struct Variable;
struct Assign;
struct Logical;
struct Call;
struct Get;
struct Set;
struct This;
struct Super;

//ExprVisitor is a class of method that every visitor should implements
class ExprVisitor {
public:
    virtual any visitTernaryExpr(shared_ptr<Ternary> expr) = 0;
    virtual any visitBinaryExpr(shared_ptr<Binary> expr) = 0;
    virtual any visitGroupingExpr(shared_ptr<Grouping> expr) = 0;
    virtual any visitLiteralExpr(shared_ptr<Literal> expr) = 0;
    virtual any visitUnaryExpr(shared_ptr<Unary> expr) = 0;
    virtual any visitVariableExpr(shared_ptr<Variable> expr) = 0;
    virtual any visitAssignExpr(shared_ptr<Assign> expr) = 0;
    virtual any visitLogicalExpr(shared_ptr<Logical> expr) = 0;
    virtual any visitCallExpr(shared_ptr<Call> expr) = 0;
    virtual any visitGetExpr(shared_ptr<Get> expr) = 0;
    virtual any visitSetExpr(shared_ptr<Set> expr) = 0;
    virtual any visitThisExpr(shared_ptr<This> expr) = 0;
    virtual any visitSuperExpr(shared_ptr<Super> expr) = 0;
    virtual ~ExprVisitor() = default;
};

//Expr itself and combinations of expr are data structures
//The constructor will take the data away, by the way.
struct Expr {
    virtual any accept(ExprVisitor& visitor) = 0;
};

struct Ternary: Expr, public enable_shared_from_this<Ternary> {
    Ternary(shared_ptr<Expr> condition,
                Token op1, shared_ptr<Expr> thenBranch,
                Token op2, shared_ptr<Expr> elseBranch)
                : condition{move(condition)},
                  op1{move(op1)}, thenBranch{move(thenBranch)},
                  op2{move(op2)}, elseBranch{move(elseBranch)}
                {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitTernaryExpr(shared_from_this());
    }

    const shared_ptr<Expr> condition;
    const Token op1;
    const shared_ptr<Expr> thenBranch;
    const Token op2;
    const shared_ptr<Expr> elseBranch;
};

struct Binary: Expr, public enable_shared_from_this<Binary> {
    Binary(shared_ptr<Expr> left, Token op, shared_ptr<Expr> right)
    : left{move(left)}, op{move(op)}, right{move(right)}
    {}

    any accept(ExprVisitor& visitor) override {
        return visitor.visitBinaryExpr(shared_from_this());
    }

    const shared_ptr<Expr> left;
    const Token op;
    const shared_ptr<Expr> right;
};

struct Grouping: Expr, public enable_shared_from_this<Grouping> {
    explicit Grouping(shared_ptr<Expr> expression)
    : expression{move(expression)}
    {}

    any accept(ExprVisitor& visitor) override {
        return visitor.visitGroupingExpr(shared_from_this());
    }

    const shared_ptr<Expr> expression;
};

struct Literal: Expr, public enable_shared_from_this<Literal> {
    explicit Literal(any value)
    : value{move(value)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitLiteralExpr(shared_from_this());
    }

    const any value;
};

struct Unary: Expr, public enable_shared_from_this<Unary> {
    Unary(Token op, shared_ptr<Expr> right)
    : op{move(op)}, right{move(right)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitUnaryExpr(shared_from_this());
    }

    const Token op;
    const shared_ptr<Expr> right;
};

struct Variable: Expr, public enable_shared_from_this<Variable> {
    explicit Variable(Token name)
    : name{move(name)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitVariableExpr(shared_from_this());
    }

    const Token name;
};

struct Assign: Expr, public enable_shared_from_this<Assign> {
    Assign(Token name, shared_ptr<Expr> value)
    : name{move(name)}, value{move(value)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitAssignExpr(shared_from_this());
    }

    const Token name;
    const shared_ptr<Expr> value;
};

struct Logical: Expr, public enable_shared_from_this<Logical> {
    Logical(shared_ptr<Expr> left, Token op, shared_ptr<Expr> right)
    : left{move(left)}, op{move(op)}, right{move(right)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitLogicalExpr(shared_from_this());
    }

    const shared_ptr<Expr> left;
    const Token op;
    const shared_ptr<Expr> right;
};

struct Call: Expr, public enable_shared_from_this<Call> {
    Call(shared_ptr<Expr> callee, Token paren, vector<shared_ptr<Expr>> arguments)
    : callee{move(callee)}, paren{move(paren)}, arguments{move(arguments)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitCallExpr(shared_from_this());
    }

    const shared_ptr<Expr> callee;
    const Token paren;
    const vector<shared_ptr<Expr>> arguments;
};

struct Get: Expr, public enable_shared_from_this<Get> {
    Get(shared_ptr<Expr> object, Token name)
    : object{move(object)}, name{move(name)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitGetExpr(shared_from_this());
    }

    const shared_ptr<Expr> object;
    const Token name;
};

struct Set: Expr, public enable_shared_from_this<Set> {
    Set(shared_ptr<Expr> object, Token name, shared_ptr<Expr> value)
    : object{move(object)}, name{move(name)}, value{move(value)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitSetExpr(shared_from_this());
    }

    const shared_ptr<Expr> object;
    const Token name;
    const shared_ptr<Expr> value;
};

struct This: Expr, public enable_shared_from_this<This> {
    explicit This(Token keyword)
    : keyword{move(keyword)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitThisExpr(shared_from_this());
    }

    const Token keyword;
};

struct Super: Expr, public enable_shared_from_this<Super> {
    Super(Token keyword, Token method)
    : keyword{move(keyword)}, method{move(method)}
    {}

    any accept(ExprVisitor &visitor) override {
        return visitor.visitSuperExpr(shared_from_this());
    }

    const Token keyword;
    const Token method;
};

#endif //LOXPP_EXPR_H
